import gobject
import random
import os
import shutil

import cPickle as pickle

from fvutil.managers import objectmanager
from fvutil import fvcon
from util import config
from presence import ps

# For RubyVote
import simplejson
import sys

import logging
logger = logging.getLogger('fluidvoice.poll')

class PollQPresence(ps.ObjectPresence):
    def __init__(self, oid, name):
        ps.ObjectPresence.__init__(self, oid, name)
        
class PollVPresence(ps.ObjectPresence):
    def __init__(self, oid, name):
        ps.ObjectPresence.__init__(self, oid, name)
        
class PollRPresence(ps.ObjectPresence):
    def __init__(self, oid, name):
        ps.ObjectPresence.__init__(self, oid, name)

class PollQManager(objectmanager.ObjectManager):
    def __init__(self, my_id):
        objectmanager.ObjectManager.__init__(self, fvcon.POLL_Q, my_id)
        
    def get_active(self, do_map):
        return objectmanager.ObjectManager.get_active(self, do_map=do_map)
    
    def to_flash(self, header):
        return {'subcommand': fvcon.ADD,
                'type':fvcon.POLL_Q,
                'id': header.mid,
                'label' : header.meta[fvcon.TAGS],
                'date'  : header.created,
                'status' : fvcon.ONLINE,
                'priority' : None,
                'subject' : header.meta[fvcon.TAGS],
                'tags' : header.meta[fvcon.TAGS],
                'question' : header.meta[PollManager.QUESTION],
                'questionID' : header.meta[PollManager.QUESTION_ID],
                'category'  : header.meta[PollManager.TYPE],
                'choices'   : [c.__dict__ for c in header.meta[PollManager.CHOICES].values()],
                'senderName' : 'to do..',
                }
        
class PollVManager(objectmanager.ObjectManager):
    def __init__(self, my_id):
        objectmanager.ObjectManager.__init__(self, fvcon.POLL_V, my_id)
        
    def get_votes_for(self, poll_id):
        # TODO: Wish there was a better way of doing this.
        # Might actually have to create own table for custom search criteria..
        # kinda sucks.
        votes = self.get_all(do_map=False)
        results = []
        for vote in votes:
            if vote.meta[PollManager.VOTE_FOR] == poll_id:
                results.append(vote)
        return results
    
class PollRManager(objectmanager.ObjectManager):
    def __init__(self, my_id):
        objectmanager.ObjectManager.__init__(self, fvcon.POLL_R, my_id)
        
    def to_flash(self, header):
        f = open(header.get_file_path(), mode='rb')
        results = pickle.load(f)
        f.close()
        return {
                'id':header.mid,
                'subcommand':fvcon.ADD,
                'label':header.meta[fvcon.TAGS],
                'type':fvcon.POLL_R,
                'tags':header.meta[fvcon.TAGS],
                'question':header.meta[PollManager.QUESTION],
                'results': results,
                'poll_type':header.meta[PollManager.TYPE],
                'src':header.src,
                }
        
class Poll:
    PLURALITY = 0
    APPROVAL = 1
    CONDORCET = 2

class Choice:
    
    def __init__(self, id, text='', relative_audio_path=''):
        if not text and not relative_audio_path:
            raise ValueError # Can't have a choice with no text and no audio_path
        self.text = text
        self.relative_audio_path = relative_audio_path
        self.id = id
    
    
        
# TODO:
# - Bundling, unbundling
# - Add creator metadata
# - Resolve relative/absolute paths
# - Continue writing send/receive code
class PollManager(gobject.GObject):
    
    CREATOR = 1870
    QUESTION = 1871
    CHOICES = 1872
    TYPE = 1873
    VOTE_FOR = 1874
    RESULT_FOR = 1875
    QUESTION_ID = 1876 # The audio id of the question
    POLL_CREATOR = 1878
    
    __gsignals__ = {
        'created_poll' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                (gobject.TYPE_INT,gobject.TYPE_INT, )),
        'received_poll' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                (gobject.TYPE_PYOBJECT,)),
        'submitted_pollvote' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                (gobject.TYPE_INT, gobject.TYPE_INT,)),
        'received_pollresults' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                (gobject.TYPE_PYOBJECT,)),
    }
        
    def __init__(self, async, my_id, mixer):
        gobject.GObject.__init__(self)
        self.__q_manager = PollQManager(my_id)
        self.__v_manager = PollVManager(my_id)
        self.__r_manager = PollRManager(my_id)
        
        self.__async = async
        self.__my_id = my_id
        self.__mixer = mixer
        
        self.__root = config.get('async.folders', fvcon.POLL_Q)
        self.__temp_path = self.__root + '/temp/'
        
        if os.path.exists(self.__temp_path):
            # Remove the contents of the temp folder because there could
            # be a lot of old recordings that we really don't care about at all.
            for file in os.listdir(self.__temp_path):
                os.remove(os.path.join(self.__temp_path, file))
        else:
            os.makedirs(self.__temp_path)
        
        self.__active = self.__q_manager.get_active(False)
        logger.debug('Active: %s', str(self.__active))
        
        # Add all the active poll audio choices to the mixer
        for active in self.__active:
            poll_id = active.mid
            question_id = active.meta[PollManager.QUESTION_ID]
            if question_id is not None:
                props = {}
                props['status'] = fvcon.ONLINE
                props['id'] = question_id
                props['local_path'] = os.path.join(
                                        self.__bundle_root(poll_id),
                                        'question.audio')
                logger.debug('Adding input for question %i',question_id)
                self.__mixer.onAddInput(self, props)
                
            for (id, choice) in active.meta[PollManager.CHOICES].iteritems():
                props = {}
                props['status'] = fvcon.ONLINE
                props['id'] = id
                props['local_path'] = os.path.join(
                                        self.__bundle_root(poll_id),
                                        choice.relative_audio_path)
                logger.debug('Adding input for poll choice %i',id)
                self.__mixer.onAddInput(self, props)
        
        self.__rubyvote_dir = os.path.join(os.path.dirname(__file__), 'rubyvote')
        assert os.path.exists(self.__rubyvote_dir)
        self.__rubyvote_path =  'rubyvote.rb'
        assert os.path.exists(os.path.join(self.__rubyvote_dir, self.__rubyvote_path))
        
        self.__poll_expiration_check =  gobject.timeout_add(10000, self.__check_expired)
        
    def get_poll(self, pid):
        return self.__q_manager.get(pid)
    
    def get_result(self, rid):
        return self.__r_manager.get(rid)
    
    def get_all(self):
        return self.__q_manager.get_active(True) + self.__r_manager.get_all()
     
    def __temp_audio_choice_path(self, choice_id):
        """
            Way of determining audio path of temporary audio file
        """
        return self.__temp_path + '/' + str(choice_id) + '.audio'
                   
    def __bundle_root(self, poll_id):
        """
            Root folder of poll bundle
        """
        return self.__root + '/' + str(poll_id) + '/'
                    
    def __bundled_audio_path(self, poll_id, choice_counter):
        """
            Where a particular choice audio file is stored
        """
        return self.__bundle_root(poll_id) + '/choice' + str(choice_counter) + '.audio'
    
    def __pack_audio(self, question_path, choices, poll_id):
        audio = {}
        
        if os.path.exists(question_path):
            f = open(question_path, mode='rb')
            question_data = f.read()
            f.close()
            audio['question'] = question_data
            
        bundle_root = self.__bundle_root(poll_id)
        for (id, choice) in choices.iteritems():
            if choice.relative_audio_path:
                f = open(os.path.join(bundle_root,
                                      choice.relative_audio_path), mode='rb')
                audio_data = f.read()
                f.close()
            else:
                audio_data = ''
            audio[id] = audio_data
        poll_path = os.path.join(bundle_root, 'poll' + str(poll_id) + '.poll')
        f = open(poll_path, mode='wb')
        pickled = pickle.dump(audio, f, pickle.HIGHEST_PROTOCOL)
        f.close()
        return poll_path
    
    def __unpack_audio(self, audio, choices, poll_id, questionID):
        bundle_root = self.__bundle_root(poll_id)
        
        if 'question' in audio:
            question_path = os.path.join(bundle_root, str(questionID) + '.audio')
            f = open(question_path, mode='wb')
            f.write(audio['question'])
            f.close()
            del audio['question']
            
        audio_ids = []
        for (id, audio) in audio.iteritems():
            relative_audio_path = choices[id].relative_audio_path
            f = open(os.path.join(bundle_root, relative_audio_path), mode='wb')
            f.write(audio)
            f.close()
            audio_ids.append(id)
        return audio_ids
        
    
    def send_question(self, data):
        logger.debug('send_question: %s',str(data))
        
        poll_id = random.randint(0,10000000)
        
        # Make the directory structure for this poll
        os.makedirs(self.__bundle_root(poll_id))
        
        # Get the question into question.audio
        question_id = data['questionID']
        temp_question_path = self.__temp_audio_choice_path(question_id)
        question_path = os.path.join(self.__bundle_root(poll_id), 'question.audio')
        if os.path.exists(temp_question_path):
            shutil.move(temp_question_path, question_path)
        else:
            # No question
            question_id = None
            
        # Construct the choices
        flash_choices = data['choices']
        choices = {}
        choice_counter = -1
        for choice in flash_choices:
            # Move the audio around so it's in the right place
            temp_audio_path = self.__temp_audio_choice_path(choice['id'])
            if os.path.exists(temp_audio_path):
                choice_counter += 1
                audio_path = self.__bundled_audio_path(poll_id, choice_counter)
                shutil.move(temp_audio_path, audio_path)
            else:
                audio_path = ''
                
            choice = Choice(choice['id'], choice['text'], audio_path)
            choices[choice.id] = choice
            
        poll_path = self.__pack_audio(question_path, choices, poll_id)
                
        dests = data['recipients']
        meta = {
                PollManager.CREATOR: self.__my_id,
                PollManager.QUESTION: data['question'],
                PollManager.CHOICES: choices,
                PollManager.TYPE: data['poll_type'],
                PollManager.QUESTION_ID: question_id,
                fvcon.TAGS: data['tags'],
                }
        # TODO: Get rid of expiration delta here. 
        # Just put in so polls persist for testing
        header = self.__async.send_message(poll_path, fvcon.POLL_Q, dests, meta, expiration_delta=180, explicit_mid=poll_id)
        logger.debug('Sent poll question mid: %s', poll_id)
        self.__q_manager.add(header)
        self.__active.append(header)
        self.emit('created_poll', header.mid, fvcon.POLL_Q)
    
    def send_vote(self, data):
        logger.debug('send_vote %s',str(data))
        vote_for_poll_id = data['id']
        votes = data['votes']
        
        vote_path = os.path.join(self.__bundle_root(vote_for_poll_id),
            'vote-' + str(vote_for_poll_id) + '-' + str(self.__my_id) + '.vote')
        f = open(vote_path, mode='wb')
        pickle.dump(votes, f, pickle.HIGHEST_PROTOCOL)
        f.close()
        
        poll = self.__q_manager.get(vote_for_poll_id, do_map=False)
        dests = [poll.src]
        
        meta = {
            PollManager.VOTE_FOR : vote_for_poll_id,
            fvcon.TAGS: '',
            PollManager.POLL_CREATOR : poll.src,
            }
                
        header = self.__async.send_message(vote_path, fvcon.POLL_V, dests, meta)
        self.__v_manager.add(header)
        self.emit('submitted_pollvote', header.mid, fvcon.POLL_V)
    
    def send_result(self, data):
        logger.debug('send_result: %s',str(data))
        poll = data['poll']
        dests = poll.dests
        meta = {
                fvcon.TAGS : 'Results: ' + poll.meta[fvcon.TAGS],
                PollManager.QUESTION: poll.meta[PollManager.QUESTION],
                PollManager.QUESTION_ID: poll.meta[PollManager.QUESTION_ID],
                PollManager.TYPE : poll.meta[PollManager.TYPE],
                PollManager.RESULT_FOR: poll.mid,
                }
        
        results = data['results']
        def to_text(result):
            result['candidate'] = poll.meta[PollManager.CHOICES][int(result['candidate'])].text
            return result
        
        results = map(to_text, results)
        result_path = os.path.join(self.__bundle_root(poll.mid),
            'results')
        f = open(result_path, mode='wb')
        pickle.dump(data['results'], f, pickle.HIGHEST_PROTOCOL)
        f.close()
        header = self.__async.send_message(result_path, fvcon.POLL_R, dests, meta)
        self.__r_manager.add(header)
        self.emit('received_pollresults', self.__r_manager.to_flash(header))
        logger.debug('Sent results')
        
    # Receivers
    def receive_question(self, header):
        logger.debug('received question %i', header.mid)
        self.__q_manager.add(header)
        
        f = open(header.get_file_path(), mode='rb')
        audio = pickle.load(f)
        f.close()      
        
        choices = header.meta[PollManager.CHOICES]
        poll_id = header.mid
        
        question_id = header.meta[PollManager.QUESTION_ID]
        audio_ids = self.__unpack_audio(audio, choices, poll_id, question_id)
               
        for id in audio_ids:
            props = {}
            props['status'] = fvcon.ONLINE
            props['id'] = id
            props['local_path'] = os.path.join(
                                    self.__bundle_root(poll_id),
                                    choices[id].relative_audio_path)
            logger.debug('Adding input for poll choice %i',id)
            self.__mixer.onAddInput(self, props)
        if question_id:
            props = {}
            props['status'] = fvcon.ONLINE
            props['id'] = question_id
            props['local_path'] = os.path.join(
                                               self.__bundle_root(poll_id),
                                               str(question_id) + '.audio')    
            logger.debug('Adding input for poll question %i',question_id)
            self.__mixer.onAddInput(self, props)
            
        self.emit('received_poll', self.__q_manager.to_flash(header))
    
    def receive_vote(self, header):
        print 'received vote for poll id',header.meta[PollManager.VOTE_FOR]
        logger.debug('received vote for poll id %i', header.meta[PollManager.VOTE_FOR])
        if header.meta[PollManager.POLL_CREATOR] == self.__my_id:
            print 'added'
            self.__v_manager.add(header)
    
    def receive_result(self, header):
        logger.debug('received result for poll id %i', header.meta[PollManager.RESULT_FOR])
        self.__r_manager.add(header)
        self.emit('received_pollresults', self.__r_manager.to_flash(header))
    
    def on_start_record(self, obj, data):
        logger.debug('on_start_record %s',str(data))
        rec_data = {'id':data['id'],
                    'filename':self.__temp_audio_choice_path(data['id']),
                    }      
        self.__mixer.onStartRecord(None, rec_data)

    def on_stop_record(self, obj, data):
        logger.debug('on_stop_record %s',str(data))
        rec_data = {'id':data['id'],
                    'filename':self.__temp_audio_choice_path(data['id']),
                    }      
        self.__mixer.onStopRecord(self, rec_data)
    
    def on_start_play(self, obj, data):
        logger.debug('on_start_play %s',str(data))
        play_data = {'id':data['id']}
        self.__mixer.onStartPlay(self, play_data)
    
    def on_stop_play(self, obj, data):
        logger.debug('on_stop_play %s',str(data))
        play_data = {'id':data['id']}
        self.__mixer.onStopPlay(self, play_data)
    
    def __check_expired(self):
        """
            Called via gobject timeout. Gets rid of expired polls.
        """
        #print 'check-expired'
        new_active = []
        for poll in self.__active:
            import time
            print poll.expires - time.time(),'remaining on ',poll.mid
            if poll.is_expired():
                logger.info('poll expired')
                vote_headers = self.__v_manager.get_votes_for(poll.mid)
                if vote_headers:
                    votes = []
                    for vote_header in vote_headers:
                        f = open(vote_header.get_file_path(), mode='rb')
                        choice_ids = pickle.load(f)
                        f.close()
                        #print 'loaded',choice_ids
                        votes.append(','.join(map(str, choice_ids)))
                    vote_string = ';'.join(map(str,votes))
                    logger.debug('Vote string: %s',vote_string)
                    prev_dir = os.getcwd()
                    # Need to change working directory to 
                    # run rubyvote.. stupid
                    os.chdir(self.__rubyvote_dir)
                    command = 'ruby %s %i %s' % (self.__rubyvote_path,
                                                 poll.meta[PollManager.TYPE], vote_string)
                    rubyvote = os.popen(command, 'r')
                    sys.stdout.flush()
                    result = rubyvote.readline()
                    rubyvote.close()
                    # Change dir back
                    os.chdir(prev_dir)
                    print 'Result: ', result
                    results = simplejson.loads(result)
                    logger.debug('results: %s', results)
                    data = {
                            'results':results,
                            'poll':poll,
                            }
                    self.send_result(data)
            else:
                new_active.append(poll)
        self.__active = new_active
        return True
    
gobject.type_register(PollManager)